package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.common.UAFCommonRuntimeException;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFRegFinishProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertRegFinishProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPRegFinishProcessor;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.ReceiveReqPayload;
import com.gmrz.uaf.servlet.v1.request.RegSendContext;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.uas.plugin.exception.PluginGoOnBusinessException;
import com.gmrz.util.Convert;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Type;
import java.util.*;

public class UAFRegResponseHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(UAFRegResponseHandler.class);

    /**
     * {
     * "uafResponse":"[{\"assertions\":[{\"assertion\":\"AT5mAwM-1QALLgkAMDAxQiMwMDAxDi4HAAABAQEAAAEKLiAAAmmSQDL3-0
     * HEm8O-QFKlJtRXgCpROXAGYONDLBWWBKgJLiAAx_QihRsjv_Jlssx5VRn6foPsQqx95pDlW2V9uBWMZ8UNLggADQAAAAUAAAAMLkEABNtQbWrh
     * gm_CZwK6xDOF4ZAYixWOYOZJKVWbkx9weTcpARtjIsjbYIIkdy17m_JqvPa8BAbcB0lhMQMBkI7wFOkEASAAZWNzOIpfN0UEVQLva_5w1UC3el
     * 95GWssIXVFwYu0TIEHPokCBi5AADSvZiVL5t15ztvxwQF_lgoIFd_wuUqpwwwnt20G-rctlkEpEU0h5X0YddHWqoYid-Y4OmKLwhTC-0jzLY9m
     * 4aAFLkECMIICPTCCAeQCAQcwCQYHKoZIzj0EATCBkDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nZG9uZzERMA8GA1UEBwwIRG9uZ2d1YW
     * 4xFzAVBgNVBAoMDnd3dy5odWF3ZWkuY29tMQwwCgYDVQQLDANSJkQxETAPBgNVBAMMCEFkbWluIENBMSAwHgYJKoZIhvcNAQkBFhFtb2JpbGVA
     * aHVhd2VpLmNvbTAeFw0xNTEwMTAwNzIzMjhaFw0yNTEwMDcwNzIzMjhaMIHFMQswCQYDVQQGEwJDTjESMBAGA1UECAwJR3Vhbmdkb25nMREwDw
     * YDVQQHDAhEb25nZ3VhbjErMCkGA1UECgwiSHVhd2VpIERldmljZSAoRG9uZ2d1YW4pIENvLiwgTHRkLjEsMCoGA1UECwwjVGVybWluYWwgU2Vj
     * dXJpdHkgRW5naW5lZXJpbmcgRGVwdC4xEjAQBgNVBAMMCTAwMUIjMDAwMTEgMB4GCSqGSIb3DQEJARYRbW9iaWxlQGh1YXdlaS5jb20wWTATBg
     * cqhkjOPQIBBggqhkjOPQMBBwNCAARXDBWxd1QKtMo9W2pbTQ-H0Yh_OzNpBAdYYQhxLVQaHL2Pkydh2KGvHA2YpAemanMJCvHTP2TYGB_HKAO_9
     * PNXMAkGByqGSM49BAEDSAAwRQIgdSDSgMiwi8FODnu_fzxh_1hMcijKqCfsHmHurm2DWzwCIQCf7lpomj2_jTbsvAa9mg5T2UMh7wpsXF48pHgr
     * A-AljA\",\"assertionScheme\":\"UAFV1TLV\"}],\"fcParams\":\"eyJhcHBJRCI6Imh0dHBzOi8vdGVzdC5ub2tub2tsYWJzLmNuOjg0
     * NDMvVUFGU2FtcGxlUHJveHkvdWFmL2ZhY2V0cy51YWYiLCJjaGFsbGVuZ2UiOiJkY0RuTlE4cWVncVFjSnlwMFB5LW9ja1hqaElnSUd5M2N5dlN
     * FNWR1UnR3IiwiY2hhbm5lbEJpbmRpbmciOnt9LCJmYWNldElEIjoiYW5kcm9pZDphcGsta2V5LWhhc2g6cWh1QVVrM2pLSWVvR3MwSEhicHN1MG
     * 9DT2cwIn0\",\"header\":{\"appID\":\"https:\/\/test.noknoklabs.cn:8443\/UAFSampleProxy\/uaf\/facets.uaf\",\"op\"
     * :\"Reg\",\"serverData\":\"SA2Ti7mkp-4bYsamaxGKAIDfDrP3Du7E_lW-rUgpxA5_gvnOQduC8hFlSxmu53N68vwmVadBgtjOVebut_bXs
     * WZ1YsYUiMiU4LqJGJC31uQLAr4559DEuR3j6bJXZY10XIC1oDUAlbx-NE4D6QkSuRFvhrnqZ2w\",\"upv\":{\"major\":1,\"minor\":0}}}]"
     * ,
     * "context":
     * {
     * "appID":"default",
     * "opType":"00",
     * "transNo":"asdfghjklqwertyuiopzxcvbnmaasdf",
     * "userName":"whz",
     * "transType":"01",
     * "authType":"00",
     * "devices":{
     * "deviceID":"+xkVOHTXVzFOpP+YzyHsZhFbn4ldgiWXGZ\/eGL5NHB7hXTn46LeqOPfYQk+YO9QL",
     * "deviceName":"HUAWEI NXT-AL10",
     * "deviceAlias":"HWNXT",
     * "deviceType":"HUAWEI",
     * "osVersion":23,
     * "osType":"android",
     * "imei":"869906024340331",
     * "mac":"48:db:50:db:87:ad"
     * }
     * }
     * }
     *
     * @param request
     * @return {
     * "statusCode":1200,
     * "description":"OK",
     * "uafRequest":""
     * }
     */
    public String regFinish(String request) {
        LOG.info("[/reg/send][begin]");
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        Gson gson = UAFSchemaBuilder.getGson();
        FidoRespPayload<String> respPayload = new FidoRespPayload<String>();
        Type reqType = new TypeToken<ReceiveReqPayload<RegSendContext>>() {
        }.getType();

        String response = null;
        ReceiveReqPayload<RegSendContext> reqPayload = null;
        String uniqueReqKey = null;
        String hashedUserName = null;
        RegSendContext context = null;
        try {
            reqPayload = gson.fromJson(request, reqType);
            //记录日志
            logRequest(request, reqPayload.getContext().getTransNo(), reqPayload.getContext().getUserName(),
                    reqPayload.getContext().getAppID(), reqPayload.getContext().getAuthType(), reqPayload.getContext().getTransType(), Constants.Method.REG_FINISH);
            ServerContext serverContext = ServerContext.getServerContext();
            serverContext.setPerform(reqPayload.getContext().getAppID());
            ServerContext.getServerContext().setBase64(reqPayload.getContext().isBase64());
            if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !serverContext.isPerform()) {
                //验证context参数
                validate(reqPayload);
            }
            context = reqPayload.getContext();
            String uafMessage = null;
            if(ServerContext.getServerContext().isBase64()){
                uafMessage = new String(Convert.fromBase64(reqPayload.getUafResponse()));
            }else {
                uafMessage = reqPayload.getUafResponse();
            }
            Type type = new TypeToken<List<RegistrationResponse>>() {
            }.getType();
            if(reqPayload.getContext().getAuthType().compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0){
                gson = UAFSchemaBuilder.getGsonProcessCert();
            }
            List<RegistrationResponse> regResponses = gson.fromJson(uafMessage, type);
            //获取当前版本的response参数
            RegistrationResponse regResponse = null;
            if ((regResponses != null) && (!regResponses.isEmpty())) {
                if (regResponses.size() > 1) {
                    regResponse = RestValidator.compareRegRespVersions(regResponses);
                } else {
                    regResponse = regResponses.get(0);
                }
            }
            if (null == regResponse) {
                LOG.error("RegResponse is Null");
                throw new PayloadValidationException("UAFRegResponse", RestValidator.VALIDATION_ERROR.EMPTY);
            }
            // hash 用户名
            hashedUserName = generateHashedUserName(context.getUserName(), context.getRf1(), context.getRf2());
            if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !serverContext.isPerform()) {
                // 验证发起接口参数和完成接口参数是否一致
                validate(reqPayload, regResponse, hashedUserName);
            }

            if(com.gmrz.util.Strings.isNullOrEmpty(context.getInstCode())){
                context.setInstCode(context.getCustType());
            }

            UAFRegFinishProcessor processor = null;
            // 走证书
            if (reqPayload.getContext().getAuthType().compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPCertRegFinishProcessor.class);
                RegistrationAssertion assertion = regResponse.getAssertions().get(0);
                String assertionAAID = assertion.getAuthenticatorResponse().getKRD().getAAID();
                // 根据 aaid 查询身份认证规范，身份认证规范对应的数据是元数据中的数据
                AuthenticatorSpec spec = assertionAAID == null ? null : processor.getAuthenticatorSpec(assertionAAID);
                if(null != spec){
                    assertion.getAuthenticatorResponse().getKRD().setSignAlgorithm(spec.getAlgorithm());
                    assertion.getAuthenticatorResponse().getKRD().setKeyFormat(spec.getKeyFormat());
                    assertion.getAuthenticatorResponse().setAttestationType(spec.getAttestationTypesAsList().get(0));
                }
                uniqueReqKey = "uaf.unique.req."+context.getAppID()+"."+context.getDevices().getDeviceID()+"."+hashedUserName+"."+context.getAuthType()+"."+context.getTransType();
                ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
                // 获取redis分布式锁，判断是否同一设备在同一时间进行注册
                boolean lock = challengeStoreService.tryGetDistributedLock(uniqueReqKey,Constants.LOCK_VALUE,serverConfig.getChallengeValiditySeconds()*1000);
                if(!lock){
                    throw new UAFCommonRuntimeException(UAFErrorCode.PROTOCOL_UNIQUE_REQUEST_FAILED);
                }
            } else if (reqPayload.getContext().getAuthType().compareTo(Constants.AuthType.REMOTE_FACEPRINT.getAuthType()) >= 0) {
                // 走远程
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPRegFinishProcessor.class);
            } else {
                // 走本地
                processor = GuiceUtil.getProcessorInjector().getInstance(UAFRegFinishProcessor.class);
            }
            // 进行业务处理
            UAFRegFinishResultBean requests = processor.process(regResponse, hashedUserName,context);
            String uafRequest = null;
            if (null != requests && null != requests.getUpdateAuthRequest()) {
//                List<UpdateAuthRequest> list = Collections.singletonList(requests.getUpdateAuthRequest());
//                uafRequest = gson.toJson(list);
//                Map<String,String> exts = new HashMap<String, String>();
//                exts.put("keyID",requests.getUpdateAuthRequest().getPolicy().getAccepted().get(0).get(0).getKeyIDList().get(0).toString());
//                respPayload.setExts(exts);
                List<DeregisterAuthenticator> deleteLast = processor.deleteLastOne(hashedUserName, context.getAppID(), context.getDevices().getDeviceID(), context.getAuthType(), context.getTransType(),requests.getSuccessAuthenticators().get(0));
                List<DeregisterAuthenticator> deleteOtherUser = processor.deleteOtherUser(hashedUserName, context.getAppID(), context.getDevices().getDeviceID(), context.getAuthType(), context.getTransType(),requests.getSuccessAuthenticators().get(0));
                DeregisterationRequest deregisterationRequest = requests.getDeregisterationRequest();

                if(null != deleteLast){
                    if(null != deregisterationRequest.getDeregisterAuthenticators()) {
                        deregisterationRequest.getDeregisterAuthenticators().addAll(deleteLast);
                    }else {
                        deregisterationRequest.setDeregisterAuthenticators(deleteLast);
                    }
                }
                if(null != deleteOtherUser){
                    if(null != deregisterationRequest.getDeregisterAuthenticators()) {
                        deregisterationRequest.getDeregisterAuthenticators().addAll(deleteOtherUser);
                    }else {
                        deregisterationRequest.setDeregisterAuthenticators(deleteOtherUser);
                    }
                }

                if(null != deregisterationRequest && null != deregisterationRequest.getDeregisterAuthenticators() && !deregisterationRequest.getDeregisterAuthenticators().isEmpty()) {
                    String uafDeleteRequest = gson.toJson(Collections.singletonList(deregisterationRequest));
                    Extension extension = new Extension();
                    extension.setId("uafDeleteRequest");
                    extension.setData(Convert.toBase64(uafDeleteRequest.getBytes()));
                    List<Extension> listExtension = requests.getUpdateAuthRequest().getOperationHeader().getExtensions();
                    if (null != listExtension) {
                        listExtension.add(extension);
                    } else {
                        listExtension = Collections.singletonList(extension);
                    }
                    requests.getUpdateAuthRequest().getOperationHeader().setExtensions(listExtension);
                    List<String> deleteUserNames = new ArrayList<String>();
                    for(DeregisterAuthenticator deregisterAuthenticator : deregisterationRequest.getDeregisterAuthenticators()){
                        deleteUserNames.add(deregisterAuthenticator.getCustNo());
                    }
                    Map<String,String> exts = new HashMap<String, String>();
                    exts.put("deleteUserNames",gson.toJson(deleteUserNames));
                    respPayload.setExts(exts);
                }

                List<UpdateAuthRequest> list = Collections.singletonList(requests.getUpdateAuthRequest());
                uafRequest = gson.toJson(list);
            } else if (null != requests && null != requests.getDeregisterationRequest()) {
                // 本地认证才返回服务器注销报文
                if (requests.getDeregisterationRequest().getDeregisterAuthenticators() != null && !requests.getDeregisterationRequest().getDeregisterAuthenticators().isEmpty()) {
                    List deregisterationRequests = requests.getDeregisterationRequest().getDeregisterAuthenticators();
                    uafRequest = gson.toJson(deregisterationRequests);
                }
            }
            if(ServerContext.getServerContext().isBase64() && com.gmrz.util.Strings.isNotBlank(uafRequest)) {
                respPayload.setUafRequest(Convert.toBase64(uafRequest.getBytes()));
            }else {
                respPayload.setUafRequest(uafRequest);
            }
            respPayload.setStatusCode(Constants.StatusCode.OK.getCode());
            respPayload.setDescription("OK");
            response = gson.toJson(respPayload);
        } catch (PluginGoOnBusinessException ex) {
            response = buildFidoRespFromException(respPayload, ex);
            try {
                delCache(context.getAppID(), context.getDevices().getDeviceID(), hashedUserName, context.getAuthType(), context.getTransType());
            }catch (Exception e){

            }
        } catch (Exception ex) {
            LOG.error("regFinish error", ex);
            response = buildFidoRespFromException(respPayload, ex);
            try {
                delCache(context.getAppID(), context.getDevices().getDeviceID(), hashedUserName, context.getAuthType(), context.getTransType());
            }catch (Exception e){
                LOG.error("del cache error",e);
            }
        }
        LOG.info("[/reg/send][end]");
        return response;
    }

    /**
     * 验证 context 参数
     *
     * @param payload
     * @throws PayloadValidationException
     */
    void validate(ReceiveReqPayload<RegSendContext> payload) throws PayloadValidationException {

        if ((payload == null) || (payload.getContext() == null)) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        if ((payload == null) || (payload.getContext().getDevices() == null)) {
            throw new PayloadValidationException("deviceID", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        if ((null == payload) || (Strings.isNullOrEmpty(payload.getUafResponse()))) {
            LOG.error("Payload does not contains UAFRegResponse");
            throw new PayloadValidationException("UAFRegResponse", RestValidator.VALIDATION_ERROR.EMPTY);
        }

        RegSendContext context = payload.getContext();

        RestValidator.validate("uafResponse", payload.getUafResponse());
        RestValidator.validate("transNo", context.getTransNo());
        RestValidator.validate("userName", context.getUserName());
        RestValidator.validate("appID", context.getAppID());
        RestValidator.validate("authType", context.getAuthType());
        RestValidator.validate("transType", context.getTransType());
        RestValidator.validate("opType", context.getOpType());
        RestValidator.validate("rf1", context.getRf1());
        RestValidator.validate("rf2", context.getRf2());
        RestValidator.validate("mobile", context.getMobile());
        RestValidator.validate("ext", context.getExt());
        RestValidator.validate("custType", context.getCustType());
        RestValidator.validate("regFrom", context.getRegFrom());
        RestValidator.validate("instCode", context.getInstCode());


        RestValidator.validate("deviceID", context.getDevices().getDeviceID());
        RestValidator.validate("deviceName", context.getDevices().getDeviceName());
        RestValidator.validate("deviceAliasName", context.getDevices().getDeviceAliasName());
        RestValidator.validate("deviceType", context.getDevices().getDeviceType());
        RestValidator.validate("osType", context.getDevices().getOsType());
        RestValidator.validate("osVersion", context.getDevices().getOsVersion());

        RestValidator.validateDevices(context.getDevices());
    }

    /**
     * 验证发起接口参数和完成接口参数是否一致
     *
     * @param payload
     * @param regResponse
     * @param hashedUserName
     * @throws ChallengeStoreException
     * @throws PayloadValidationException
     */
    void validate(ReceiveReqPayload<RegSendContext> payload, RegistrationResponse regResponse, String hashedUserName) throws ChallengeStoreException, PayloadValidationException {
        RegSendContext context = payload.getContext();
        String challenge = Convert.toBase64(regResponse.getFinalChallengeParams().getServerChallenge().getChallenge());
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        String paramCache = challengeStoreService.getRedisValue(Constants.UAP_REQ_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName);
        if (null == paramCache || "".equals(paramCache.trim())) {
            throw new PayloadValidationException("send context and receive context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
        } else {
            String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                    context.getAppID(), context.getDevices().getDeviceID(), context.getTransType(), context.getAuthType(), context.getChallenge());
            LOG.debug("req validate context param:" + param + "---cache:" + paramCache);
            if (!param.equals(paramCache)) {
                LOG.error("req validate context param:" + param + "---cache:" + paramCache);
                throw new PayloadValidationException("send context and receive context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
            }
        }
    }
}
